package br.com.biblioteca.dao;

import br.com.biblioteca.exceptions.ConflictExcetion;
import br.com.biblioteca.exceptions.NotFoundException;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;


import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.ProjectionList;

/**
 *
 * @author João Sávio C. Longo - joaosavio@gmail.com
 */
public abstract class GenericHibernateDAO<T, ID extends Serializable> implements IGenericDAO<T, ID> {

    private static Logger LOG = Logger.getLogger(GenericHibernateDAO.class);
    private Class<T> persistentClass;
    private Session session;
    private Transaction transaction;

    public GenericHibernateDAO() {
        this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public void add(T t) {
        try {            
            getSession().save(t);
            beginTransaction();
            commit();
        } catch (HibernateException e) {
            LOG.error("Hibernate error when add.", e);
            rollback();
            throw e;
        } finally {
            close();
        }
    }

    public void update(T t) throws NotFoundException {
        try {
            getSession().update(t);
            beginTransaction();
            commit();
        } catch (HibernateException e) {
            LOG.error("Hibernate error when update.", e);
            rollback();
            throw e;
        } finally {
            close();
        }
    }

    public void delete(T t) throws NotFoundException {
        try {            
            getSession().delete(t);
            beginTransaction();
            commit();
        } catch (HibernateException e) {
            LOG.error("Hibernate error when delete.", e);
            rollback();
            throw e;
        } finally {
            close();
        }
    }

    public T getById(ID id) {
        try {
            T t = (T) getSession().get(persistentClass, id);
            if (t == null) {
                throw new NotFoundException(t.getClass());
            }
            return t;
        } catch (HibernateException e) {
            LOG.error("Hibernate error when getById.", e);
            throw e;
        } finally {
            close();
        }
    }

    public List<T> findByCriteria(List<Criterion> list, ProjectionList projectionList) {
        try {
            Criteria criteria = getSession().createCriteria(persistentClass);
            for (int i = 0; i < list.size(); i++) {
                criteria.add(list.get(i));
            }
            if (projectionList != null) {
                criteria.setProjection(projectionList);
            }
            return criteria.list();
        } catch (HibernateException e) {
            LOG.error("Hibernate error when findByCriteria.", e);
            throw e;
        } finally {
            close();
        }
    }

    public List<T> listAll() {
        try {
            return getSession().createCriteria(persistentClass).list();
        } catch (HibernateException e) {
            LOG.error("Hibernate error when listAll.", e);
            throw e;
        } finally {
            close();
        }
    }

    public List<T> list(int begin, int length) {
        try {
            return getSession().createCriteria(persistentClass).setMaxResults(length).setFirstResult(begin).list();
        } catch (HibernateException e) {
            LOG.error("Hibernate error when list.", e);
            throw e;
        } finally {
            close();
        }
    }

    public Long count() {
        try {
            String hql = "select count(*) from " + persistentClass.getSimpleName();
            Query q = getSession().createQuery(hql);
            return (Long) q.uniqueResult();
        } catch (HibernateException e) {
            throw e;
        } finally {
            close();
        }
    }

    public Long count(String column, String where) {
        try {
            String hql = "select count(" + column + ") from " + persistentClass.getSimpleName() + " " + where;
            Query q = getSession().createQuery(hql);
            return (Long) q.uniqueResult();
        } catch (HibernateException e) {
            throw e;
        } finally {
            close();
        }
    }

    public Double sum(String column) {
        try {
            String hql = "select sum(" + column + ") from " + persistentClass.getSimpleName();
            Query q = getSession().createQuery(hql);
            return (Double) q.uniqueResult();
        } catch (HibernateException e) {
            throw e;
        } finally {
            close();
        }
    }

    public Double sum(String column, String where) {
        try {
            String hql = "select sum(" + column + ") from " + persistentClass.getSimpleName() + " " + where;
            Query q = getSession().createQuery(hql);
            return (Double) q.uniqueResult();
        } catch (HibernateException e) {
            throw e;
        } finally {
            close();
        }
    }

    public Double avg(String column) {
        try {
            String hql = "select avg(" + column + ") from " + persistentClass.getSimpleName();
            Query q = getSession().createQuery(hql);
            return (Double) q.uniqueResult();
        } catch (HibernateException e) {
            throw e;
        } finally {
            close();
        }
    }

    public Double avg(String column, String where) {
        try {
            String hql = "select avg(" + column + ") from " + persistentClass.getSimpleName() + " " + where;
            Query q = getSession().createQuery(hql);
            return (Double) q.uniqueResult();
        } catch (HibernateException e) {
            throw e;
        } finally {
            close();
        }
    }

    public void close() {
        if (session.isOpen()) {
            session.close();
        }
    }

    public void rollback() {
        if (hasTransaction()) {
            try {
                transaction.rollback();
                transaction = null;
            } catch (HibernateException e) {
                LOG.error("Hibernate error when rollback.", e);
                throw e;
            }
        }
    }

    public void add(List<T> list) {
        try {
            for (int i = 0; i < list.size(); i++) {
                getSession().save(list.get(i));
                beginTransaction();
                commit();
            }
        } catch (HibernateException e) {
            LOG.error("Hibernate error when add a list.", e);
            throw e;
        } finally {
            close();
        }
    }

    public void deleteAll() {
        try {
            String hql = "delete from " + persistentClass.getSimpleName();
            Query q = getSession().createQuery(hql);
            q.executeUpdate();
        } catch (HibernateException e) {
            LOG.error("Hibernate error when delete all.", e);
            rollback();
            throw e;
        } finally {
            close();
        }
    }

    public List<T> findByExample(T exampleInstance, List<String> excludeProperty) {
        try {
            Criteria crit = getSession().createCriteria(getPersistentClass()).setLockMode(LockMode.UPGRADE);
            Example example = Example.create(exampleInstance);
            for (String exclude : excludeProperty) {
                example.excludeProperty(exclude);
            }
            crit.add(example);
            return crit.list();
        } catch (HibernateException e) {
            LOG.error("Hibernate error when find by example.", e);
            rollback();
            throw e;
        } finally {
            close();
        }

    }

    public List hqlQuery(String hql) throws Exception {
        try {
            Query q = getSession().createQuery(hql);
            return q.list();
        } catch (Exception e) {
            LOG.error("Hql query error.", e);
            throw e;
        } finally {
            close();
        }
    }

    public List sqlQuery(String sql) throws Exception {
        try {
            Query q = getSession().createSQLQuery(sql);
            return q.list();
        } catch (Exception e) {
            LOG.error("SQL query error.", e);
            throw e;
        } finally {
            close();
        }
    }

    public void setSession(Session session) {
        this.session = session;
    }

    protected Session getSession() {
        if (!session.isOpen()) {
            session = HibernateUtil.getSession();
        }
        return session;
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    public void beginTransaction() {
        transaction = session.beginTransaction();
    }

    public void commit() {
        transaction.commit();
        transaction = null;
    }

    public boolean hasTransaction() {
        return transaction != null;
    }

    public void clear() {
        session.clear();
    }
}
